package com.wn.system.service.impl;
import java.math.BigDecimal;

import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.wn.common.exception.Assert;
import com.wn.common.model.bank.dto.BankCardAddDTO;
import com.wn.common.model.bank.dto.CardBindDto;
import com.wn.common.model.bank.dto.SignatureDto;
import com.wn.common.model.user.dto.LoginDto;
import com.wn.common.model.user.dto.UserRegisterDto;
import com.wn.common.model.user.vo.LoginVo;
import com.wn.common.model.user.vo.UserInfoVo;
import com.wn.common.result.enums.impl.BusinessCode;
import com.wn.common.utils.JwtTemplate;
import com.wn.sddfp.client.bank.TradeLogApi;
import com.wn.system.entity.User;
import com.wn.system.entity.UserCard;
import com.wn.system.entity.UserDetail;
import com.wn.system.mapper.UserMapper;
import com.wn.system.service.IUserCardService;
import com.wn.system.service.IUserDetailService;
import com.wn.system.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.*;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author yellow docker
 * @since 2024-09-03
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Resource
    private IUserDetailService iUserDetailService;

    @Autowired
    private IUserCardService userCardService;

    @Autowired
    private JwtTemplate jwtTemplate;

    //开启 远程调用 (把TradeLogApi 直接添加到 IOC容器中  )
    //直接进行注入 进行远程调用  (向 调用本地方法一样 )
    @Autowired
    private TradeLogApi tradeLogApi;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    //            RedisTemplate


    /**
     *  用户注册的实现类
     *
     *  1、根据手机号、密码、用户名进行注册；
     *  2、注册需要验证手机号是否重复；
     *  3、需要保存用户信息、用户详情信息
     *
     * @param userRegistDto
     */
    @Transactional(
            propagation = Propagation.REQUIRED, // 事务的传播行为
            rollbackFor = Exception.class)      // 事务回滚的异常 (如果不配置就是默认回滚RunTimeException事务 )
    @Override
    public void register(UserRegisterDto userRegistDto) {
        

        //注册需要验证手机号是否重复；
        String phone = userRegistDto.getPhone();
        //用 手机 查询用户
        User one = getOne(Wrappers.<User>lambdaQuery().eq(User::getPhone, phone));

        //不等于空 抛出异常
        if (one != null) {
            //用户已存在
            Assert.error(BusinessCode.USER_EXISTED);
        }

//        boolean flag = !Objects.isNull(one);
        saveUser(userRegistDto);
    }

    /**
     * 保存用户
     * @param userRegistDto
     */
    private void saveUser(UserRegisterDto userRegistDto) {
        //存储 用户信息、用户详情信息
        User saveUser = new User();

        //ID  mp添加 id的时候 自动 生产
        //手机号码
        saveUser.setPhone(userRegistDto.getPhone());
        //用户名
        saveUser.setUserName(userRegistDto.getUserName());
        // 密码进行加密  (注册的时候 的加密 和  登录的解密 需要保持一致 )
        String pwd = BCrypt.hashpw(userRegistDto.getPwd());
        saveUser.setPwd(pwd);
        //1 是个人   2 是企业
        saveUser.setUserType("1");
        // 默认 设置 false
        saveUser.setUserStatus(false);
        save(saveUser);

        UserDetail userDetail = new UserDetail();
        //使用 User表的id
        userDetail.setId(saveUser.getId());
        userDetail.setUserName(userRegistDto.getUserName());
        userDetail.setPhone(userRegistDto.getPhone());
        //借款没有
        userDetail.setBorrowMoney(new BigDecimal("0"));
        //用户id 自己写一个字符串就可以
        userDetail.setUserCode("UC_" + UUID.randomUUID().toString().substring(0,10));

        //用户的绑卡状态
        userDetail.setCardBindStatus(false);

        iUserDetailService.save(userDetail);
    }

    @Override
    public LoginVo login(LoginDto loginDto) {

        //  验证码登录
        //  验证码登录
        return loginDto.getIsPhoneLogin() ? phoneToLogin(loginDto) : pwdLogin(loginDto);
    }

    @Override
    public UserInfoVo findById(Long userId) {

        ExecutorService executorService = Executors.newFixedThreadPool(2);
        //01 查询用户信息  id\用户username 、用户编码
        //查询 2张表
        //查询  user         username + id
        //查询  userDetail   user_code

        //线程1

        Callable<User> task = () -> getOne(Wrappers.lambdaQuery(User.class).eq(User::getId, userId));
        // 提交任务并获取 Future 对象

//        executorService.submit      提交有返回值的线程  (Callable)
//        executorService.execute     提交 没有返回值的线程 (Runnable)

        Future<User> future = executorService.submit(task);
        // 获取查询结果（会阻塞直到任务完成）
        User user = null;
        try {
            user = future.get();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }

        //线程2
        Callable<UserDetail> task2 = () -> iUserDetailService.getOne(
                Wrappers.lambdaQuery(UserDetail.class)
                        .eq(UserDetail::getId, userId));

        Future<UserDetail> future2 = executorService.submit(task2);
        // 获取查询结果（会阻塞直到任务完成）
        UserDetail userDetail = null;
        try {
            userDetail = future2.get();  //有阻塞
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }

        // 线程1 和 线程2  执行完毕后 userinfoVo 组装数据 main
        // CompletableFuture
        //闭锁  CountDownLatch  + Thread  (线程执行完毕后 再执行main中的方法)

        //准备 userinfoVo 数据
        UserInfoVo userInfoVo = new UserInfoVo();
        userInfoVo.setUserName(user.getRealName());
        userInfoVo.setIdentityCode(user.getIdentityCode());
        userInfoVo.setUserCode(userDetail.getUserCode());

        return userInfoVo;
    }

    /**
     *
     * 绑卡的业务实现
     * 1、从token中获取userId
     * 2、校验：根据用户id查询u_user_detail用户表，根据CARD_BIND_STATUS字段判断是否已经绑卡
     *
     * 3、如果未绑卡，保存保卡信息到u_user_card表
     *
     * 4、更新用户表：把请求参数中的用户真实姓名、身份证号码更新到u_user表中
     * 5、生成请求流水号，用来访问托管代理(进行日志记录)，并更新u_user_detail表中流水号字段REQ_CODE
     *
     * 6、远程访问托管代理服务（暂不实现）
     *
     *   搞清楚表:                                字段
     *        u_user_detail 进行绑卡判断      CARD_BIND_STATUS
     *        u_user_card   今天数据添加      添加数据
     *        u_user        更新数据          real_name,id
     *        u_user_detail 进行数据          更新流水号 REQ_CODE
     *
     * @param cardBindDto
     * @return
     */
    @Override
    public SignatureDto addBankAccount(CardBindDto cardBindDto) {

        //01 获取token
        // 在 controller 中 使用  @Header
        // 在 controller 中 注入一个servletRequest  通过 getHeader 来获取token
        // 上面的写法 只能在某一个方式中使用 (servlet中不能使用)

        //RequestContextHolder  所有的request的数据的封装总成  可以从这里获取 request请求对象 (RequestContextHolder 在整个项目中进行全局使用)
        //推荐使用RequestContextHolder 来获取token
        HttpServletRequest request= ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String token = request.getHeader("token");
        Object obj = jwtTemplate.parseToken(token, "userId");
        System.out.println("obj.toString() = " + obj.toString());
        String userIdStr = obj.toString();


        // 2、校验：根据用户id查询u_user_detail用户表，根据CARD_BIND_STATUS字段判断是否已经绑卡
        UserDetail userDetail = iUserDetailService.getOne(Wrappers.lambdaQuery(UserDetail.class)
                .eq(UserDetail::getId,Long.valueOf(userIdStr)));
        //绑卡的为 true (以绑卡抛出异常)
        Assert.error(userDetail.getCardBindStatus(),BusinessCode.USER_ALREADY_BINDED);

        //3、如果未绑卡，保存保卡信息到u_user_card表
        UserCard userCard = new UserCard();

        //用户id
        userCard.setUserId(Long.valueOf(userIdStr));

        //银行卡号
        userCard.setCardNo(cardBindDto.getCardNo());
        //从用户详情中拿到手机号码
        userCard.setPhone(userDetail.getPhone());

        //状态默认是false  如果绑卡成功后 可以修改true
        userCard.setStatus(false);
        userCardService.save(userCard);

        // 4、更新用户表：把请求参数中的用户真实姓名、身份证号码更新到u_user表中
        update(Wrappers.lambdaUpdate(User.class)
                .set(User::getRealName,cardBindDto.getRealName())
                .set(User::getIdentityCode,cardBindDto.getIdentityCode())
                .eq(User::getId,Long.valueOf(userIdStr)));

        //5、生成请求流水号，用来访问托管代理(进行日志记录)，并更新u_user_detail表中流水号字段REQ_CODE
        String req_code = UUID.randomUUID().toString().substring(0, 10);
        iUserDetailService.update(
                Wrappers.lambdaUpdate(UserDetail.class)
                        .set(UserDetail::getReqCode,req_code)
                        .eq(UserDetail::getId,userDetail.getId())
        );

        //6、远程访问托管代理服务（暂不实现）

        //使用openfeign的技术
        BankCardAddDTO bankCardAddDTO = new BankCardAddDTO();

        bankCardAddDTO.setReqCode(req_code);
        bankCardAddDTO.setUserId(Long.valueOf(userIdStr));

        //托管代码 返回的数据
        SignatureDto signatureDto = tradeLogApi.proxyBindCardLog(bankCardAddDTO);

        return signatureDto;
    }

    /**
     * 密码登录
     * @param loginDto
     * @return
     */
    private LoginVo pwdLogin(LoginDto loginDto) {

        //登录的密码
        String loginSecret = loginDto.getLoginSecret();
        User user = getOne(Wrappers.lambdaQuery(User.class).eq(User::getUserName, loginDto.getLoginName()));
        //校验密码
        //数据库中的密码是加密
        boolean isLogin = BCrypt.checkpw(loginSecret,user.getPwd());
        System.out.println("isLogin = " + isLogin);
        //秘密错误抛出异常
        Assert.error(!isLogin,BusinessCode.USER_LOGIN_ERROR);

        return  getLoginVo(user);
    }

    private LoginVo getLoginVo(User user) {

        HashMap<String,Object> map = new HashMap<>();

        //不能存储敏感数据 (账号的密码 )
        map.put("userId",user.getId());
        map.put("phone",user.getPhone());

        String token = jwtTemplate.createToken(map);

        //把token 存储在redis
        String key = "TOKEN:" +user.getId();

        //token 存储在redis 中 30min
        stringRedisTemplate.opsForValue().set(
                key,
                token,
                30,
                TimeUnit.MINUTES
        );

        LoginVo loginVo = LoginVo.builder()
                .id(user.getId())
                .phone(user.getPhone())
                .userName(user.getUserName())
                .token(token)
                .build();

        return loginVo;
    }

    /**
     * 手机验证码登录
     * @param loginDto
     * @return
     *   1、校验短信验证码是否过期
     *   2、判断验证码 是否正确
     *   3、判断这个手机号码是否 存在用户
     *       1、不存在 直接注册
     *
     *       2、存在 直接返回用户
     */
    private LoginVo phoneToLogin(LoginDto loginDto) {

        //01 校验短信验证码是否过期
        String key = "SMS:" + loginDto.getLoginName();
        String redisCode = stringRedisTemplate.opsForValue().get(key);

        //redisCode 为null  抛出异常 验证码有问题
        Assert.error(StringUtils.isEmpty(redisCode),BusinessCode.USER_VERIFY_CODE_ERROR);

        //02 判断验证码 是否正确
        if (loginDto.getLoginSecret().equals(redisCode)){

            //后续操作
            //判断这个用户是否已注册
            User user = getOne(Wrappers.lambdaQuery(User.class).eq(User::getPhone, loginDto.getLoginName()));

            //如果为null 主动进行注册
            if (user == null){
                UserRegisterDto userRegisterDto = new UserRegisterDto();
                //随机一个用户名
                userRegisterDto.setUserName(UUID.randomUUID().toString().substring(0,8));
                //密码默认
                userRegisterDto.setPwd("123456");
                //手机号码
                userRegisterDto.setPhone(loginDto.getLoginName());

                //保存用户
                saveUser(userRegisterDto);

                //保存完毕后 查询一下
                user = getOne(Wrappers.lambdaQuery(User.class).eq(User::getPhone, loginDto.getLoginName()));
            }

            //已经注册过 可以直接返回登录用户
            return getLoginVo(user);

        }
        //验证码异常  抛出异常
        Assert.error(StringUtils.isEmpty(redisCode),BusinessCode.USER_VERIFY_CODE_ERROR);

        return null;
    }
}
